﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Collections;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Net;
using System.Collections.Specialized;

namespace Server
{
    public class ServerSocketClass
    {
        public const byte FIRST_FRAME = 0;
        public const byte FRAME_MODIFY = 1;
        public const byte TRANSMISSION_PAUSED = 2;

        private class ReturnTcpClient
        {
            private TcpClient __client;

            public TcpClient Client
            {
                get { return __client; }
                set
                {
                    __client = value;
                }
            }

            public ReturnTcpClient()
            {
                __client = null;
            }
        }


        private delegate void NewMessageCallback(string msg);
        private delegate void SetClipboardCallback(DataObject data);
        private delegate void SetBitmapCallback(Bitmap bmp);
        private delegate void CloseAppCallback();

        private NewMessageCallback showMessage;
        private SetBitmapCallback setBitmapcallback;

        private const int MaxThreadWidth = CaptureClass.MaxWidth / 2; /* al massimo 2 thread per larghezza */
        private const int MaxThreadHeight = CaptureClass.MaxHeight / 4; /* al massimo 4 thread per altezza --> 8 thread totali*/

        private bool windowCapture;
        private IntPtr capturedWindow;
        private Rectangle screenRectangle;

        private Bitmap lastBitmap;
        private Bitmap currentBitmap;
        private Bitmap tmpCurrent;
        private BitmapData lastBmpData;
        private BitmapData currentBmpData;

        private WaitHandle[] waitPictureThreads;

        private const ushort MAX_CLIENTS = 4;

        private volatile bool keepWorking;
        private volatile bool transmit;
        private volatile bool startCompare;
        private volatile bool userPaused;
        private volatile bool videoPaused;
        
        private UInt16 port;
        private MainForm mainForm;
        private ChatDialog chatDialog;
        private TcpListener listener;

        //Evento per svegliare il listener quando i client connessi passano da MAX_CLIENT a MAX_CLIENT-1
        private AutoResetEvent clientEvent;
        private ManualResetEvent notPausedEvent;
        private ManualResetEvent stopWaitingAndExit;  //Per gestire teerminazione thread quando si trovano in stato di wait . Negli altri casi, terminazione tramite SocketException o keepWorking = false

        //Evento per gestire il prossimo frame
        private AutoResetEvent nextFrameEvent;

        //Costrutti di sincronizzazione
        private Object usersMonitor = new Object();
        private Object bitmapMonitor = new Object(); //non so se serve
        
        private Hashtable users; //serve sinc
        private HashSet<Thread> activeThreads;
        private ushort clientNum;  //costrutto di sincronizzazione
        
        private string password;
        
        public ServerSocketClass(ushort port, MainForm mainForm, ChatDialog chatDialog, string password, AutoResetEvent nextFrameEvent, IntPtr capturedWindow)
        {
            // TODO: Complete member initialization
            this.port = port;
            this.mainForm = mainForm;
            this.chatDialog = chatDialog;
            this.chatDialog.Set_SocketInstance(this);
            this.password = password;
            this.nextFrameEvent = nextFrameEvent;

            this.windowCapture = true;
            this.capturedWindow = capturedWindow;
            
            this.listener = new TcpListener(System.Net.IPAddress.Any, port);
            this.clientNum = 0;
            this.users = new Hashtable(MAX_CLIENTS);
            this.activeThreads = new HashSet<Thread>();
            this.clientEvent = new AutoResetEvent(false);
            this.stopWaitingAndExit = new ManualResetEvent(false);
            this.notPausedEvent = new ManualResetEvent(true);
            this.keepWorking = true;
            this.startCompare = false;
            this.showMessage = new NewMessageCallback(chatDialog.newChatMessage);
            this.setBitmapcallback = new SetBitmapCallback(mainForm.setImage);
            this.transmit = false;
            this.userPaused = false;
            this.videoPaused = false;
        }

        public ServerSocketClass(ushort port, MainForm mainForm, ChatDialog chatDialog, string password, AutoResetEvent nextFrameEvent, Rectangle screenRectangle)
        {
            // TODO: Complete member initialization
            this.port = port;
            this.mainForm = mainForm;
            this.chatDialog = chatDialog;
            this.chatDialog.Set_SocketInstance(this);
            this.password = password;
            this.nextFrameEvent = nextFrameEvent;

            this.windowCapture = false;
            this.screenRectangle = screenRectangle;

            this.listener = new TcpListener(System.Net.IPAddress.Any, port);
            this.clientNum = 0;
            this.users = new Hashtable(MAX_CLIENTS);
            this.activeThreads = new HashSet<Thread>();
            this.clientEvent = new AutoResetEvent(false);
            this.stopWaitingAndExit = new ManualResetEvent(false);
            this.notPausedEvent = new ManualResetEvent(true);
            this.keepWorking = true;
            this.startCompare = false;
            this.showMessage = new NewMessageCallback(chatDialog.newChatMessage);
            this.setBitmapcallback = new SetBitmapCallback(mainForm.setImage);
            this.transmit = false;
            this.userPaused = false;
            this.videoPaused = false;
        }

        /// <summary>
        /// accetta le connessioni TCP e avvia i thread secondari relativi ai singoli client
        /// </summary>
        public void acceptConnections()
        {
            if (windowCapture)
            {
                bool minimized;
                lastBitmap = CaptureClass.CaptureWindow(capturedWindow, out minimized);
            }
            else
                lastBitmap = CaptureClass.CaptureScreen(screenRectangle);

            Thread imageThread = new Thread(new ThreadStart(imageMain));
            activeThreads.Add(imageThread);
            imageThread.Start();
            
            WaitHandle[] handles = new WaitHandle[2];
            handles[0] = stopWaitingAndExit;
            handles[1] = clientEvent;
            try
            {
                bool waitEvent = false;
                listener.Start();
                while (keepWorking)
                {
                    //se clientNnum == MAX_CLIENT --> sleep
                    if (waitEvent)
                    {
                        int which = WaitHandle.WaitAny(handles);
                        if (which == 0)
                            break;
                    }
                    waitEvent = false;
                    TcpClient c = listener.AcceptTcpClient();
                    GetUsersLock();
                    if (keepWorking) //nel frattempo keepWorking potrebbe essere diventato false a causa di una richiesta di chiusura dell'applicazione
                    {
                        clientNum++;
                        if (clientNum == MAX_CLIENTS)
                        {
                            waitEvent = true;
                        }
                        Thread t = new Thread(new ParameterizedThreadStart(chatReceive));
                        activeThreads.Add(t);
                        ReleaseUsersLock();
                        t.Start(c);
                    }
                    else
                        ReleaseUsersLock();
                }
            }
            catch(SocketException)
            {
            }
            finally
            {
           
                CloseAppCallback closeCallback = new CloseAppCallback(mainForm.CloseMe);
                try
                {
                    mainForm.Invoke(closeCallback);
                }
                catch
                {
                }
            }
        }

        private void AcceptSingleConnection(object obj)
        {
            object[] parameters = (object[])obj;
            TcpListener listen = (TcpListener) parameters[0];
            AutoResetEvent myEvent = (AutoResetEvent)parameters[1];
            ReturnTcpClient returnClient = (ReturnTcpClient)parameters[2];
            int sessid = (int)parameters[3];
            //returnClient.Client = listen.AcceptTcpClient();
            try
            {
                TcpClient client = listen.AcceptTcpClient();
                BinaryReader reader = new BinaryReader(client.GetStream());
                int recvSessId = reader.ReadInt32();
                if (recvSessId == sessid)
                {
                    returnClient.Client = client;
                    myEvent.Set();
                }
                else
                    client.Close();
            }
            catch
            {
            }
        }


        /// <summary>
        /// Verifica le credenziali del client e in caso positivo riceve i messaggi provenienti dal client
        /// </summary>
        /// <param name="o"></param>
        private void chatReceive(object o)
        {
            String user = null;
            TcpClient c = (TcpClient)o;
            TcpClient clipClient = null;
            TcpClient videoClient = null;
            Thread clipThread = null;
            StreamReader reader = null;
            StreamWriter writer = null;
            bool auth = false;
            bool locked = false;
            try
            {
                reader = new StreamReader(c.GetStream());
                writer = new StreamWriter(c.GetStream());
                user = reader.ReadLine();
                String pass = reader.ReadLine();
                
                if (pass.Equals(password))
                {
                    if (!user.Equals(""))
                    {
                        GetUsersLock();
                        locked = true;
                        if (!users.ContainsKey(user) && !user.Equals("Server"))
                        {
                            AutoResetEvent connectedEvent = new AutoResetEvent(false);

                            TcpListener clipListener = new TcpListener(IPAddress.Any, 0);
                            TcpListener videoListener = new TcpListener(IPAddress.Any, 0);

                            clipListener.Start(1);
                            videoListener.Start(1);
                            
                            int clipPort = ((IPEndPoint)(clipListener.LocalEndpoint)).Port;

                            int videoPort = ((IPEndPoint)(videoListener.LocalEndpoint)).Port;

                            Random rand = new Random();
                            int sessid = rand.Next();
                      
                            //invia messaggio conferma auth
                            writer.WriteLine("200 - Clipboard");
                            writer.Flush();
                            BinaryWriter binWr = new BinaryWriter(c.GetStream());
                            binWr.Write(sessid);
                            binWr.Write(clipPort);
                            binWr.Write(videoPort);
                            binWr.Flush();

                            ReturnTcpClient returnClient = new ReturnTcpClient();

                            object[] parameters = new object[4];
                            parameters[0] = clipListener;
                            parameters[1] = connectedEvent;
                            parameters[2] = returnClient;
                            parameters[3] = sessid;

                            ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptSingleConnection), parameters);

                            if (connectedEvent.WaitOne(5000) == false)
                            {
                                //Timeout;
                                clipListener.Stop();
                                writer.WriteLine("Timed-out");
                                writer.Flush();
                                c.Client.Close();
                                c.Close();
                                reader.Close();
                                writer.Close();
                                DecreaseClientNum();
                            }
                            else
                            {
                                writer.WriteLine("201 - Video");
                                writer.Flush();
                                clipListener.Stop();
                                connectedEvent.Reset();
                                clipClient = returnClient.Client;
                                returnClient.Client = null;
                                
                                parameters[0] = videoListener;
                                parameters[1] = connectedEvent;
                                parameters[2] = returnClient;
                                parameters[3] = sessid;

                                ThreadPool.QueueUserWorkItem(new WaitCallback(AcceptSingleConnection), parameters);

                                if (connectedEvent.WaitOne(5000) == false)
                                {
                                    //Timeout;
                                    videoListener.Stop();
                                    writer.WriteLine("Timed-out");
                                    writer.Flush();
                                    c.Client.Close();
                                    c.Close();
                                    clipClient.Close();
                                    reader.Close();
                                    writer.Close();
                                    DecreaseClientNum();
                                }
                                else
                                {
                                    videoListener.Stop();
                                    writer.WriteLine("202 - Completed");
                                    writer.Flush();

                                    videoClient = returnClient.Client;

                                    chatDialog.Invoke(showMessage, Environment.NewLine + "--SERVER INFORMATION: User '" + user + "' connected!--" + Environment.NewLine);
                                    SendMsgToAll(Environment.NewLine + "--SERVER INFORMATION: User '" + user + "' connected!--" + Environment.NewLine);

                                    ClientClass myClient = new ClientClass(c, clipClient, videoClient);

                                    users.Add(user, myClient);
                                    if (users.Count == 1)
                                        transmit = true;
                                    auth = true;
                                    // manda il primo frame del video
                                    Monitor.Enter(bitmapMonitor);
                                    
                                    BinaryWriter bmpStream = new BinaryWriter(videoClient.GetStream());
                                    using (MemoryStream ms = new MemoryStream())
                                    {
                                        lock (tmpCurrent)
                                            tmpCurrent.Save(ms, ImageFormat.Jpeg);//Png);
                                        byte[] buffer = ms.ToArray();
                                        bmpStream.Write(FIRST_FRAME);
                                        bmpStream.Write(buffer.Length);
                                        bmpStream.Write(buffer);
                                        bmpStream.Flush();
                                    }
                                    
                                    Monitor.Exit(bitmapMonitor);
                                    
                                    clipThread = new Thread(new ParameterizedThreadStart(receiveClipboard));
                                    //clipThread.SetApartmentState(ApartmentState.STA);  //Farlo solo per il thread che riceve la clipboard
                                    clipThread.Start(myClient);

                                    ReleaseUsersLock();
                                    locked = false;
                                }
                            }
                        }
                        else
                        {
                            ReleaseUsersLock();
                            locked = false;
                            //manda messaggio utente già presente
                            writer.WriteLine("409 - Conflict");
                            writer.Flush();

                            c.Client.Close();
                            c.Close();
                            reader.Close();
                            writer.Close();
                            DecreaseClientNum();
                        }
                    }
                    else
                    {
                        //nome utente non valido
                        writer.WriteLine("409 - Conflict");
                        writer.Flush();
                        c.Client.Close();
                        c.Close();
                        reader.Close();
                        writer.Close();
                        DecreaseClientNum();
                    }
                }
                else
                {
                    //pass non valida
                    writer.WriteLine("403 - Forbidden");
                    writer.Flush();
                    c.Client.Close();
                    c.Close();
                    reader.Close();
                    writer.Close();
                    DecreaseClientNum();
                }
            }
            catch
            {
                GetUsersLock();
                DecreaseClientNum();//svegliare il thread che accetta connessioni nel caso in cui il numero di connessi sia passato da 4 a 3
                if (auth == true)
                {
                    chatDialog.Invoke(showMessage, Environment.NewLine + "--SERVER INFORMATION: User '" + user + "' disconnected!--" + Environment.NewLine);
                    SendMsgToAll(Environment.NewLine + "--SERVER INFORMATION: User '" + user + "' disconnected!--" + Environment.NewLine);
                    users.Remove(user);
                }
                activeThreads.Remove(Thread.CurrentThread);
                ReleaseUsersLock();
                if(locked)
                    ReleaseUsersLock(); //se ce l'avevo già prima lo libero
                c.Client.Close();
                c.Close();
                if (clipClient != null)
                    clipClient.Close();
                if (videoClient != null)
                    videoClient.Close();
                reader.Close();
                writer.Close();
                return;
            }


            if (auth)
            {
                try
                {
                    String msg;
                    while ((msg = reader.ReadLine()) != null)
                    {
                        String fullMsg = user + ": " + msg;
                        GetUsersLock();
                        chatDialog.Invoke(showMessage, fullMsg);
                        SendMsgToAll(fullMsg);
                        ReleaseUsersLock();
                    }
                }
                catch (SocketException)
                {
                }
                catch (IOException)
                {
                }
                finally
                {
                    if (!chatDialog.IsDisposed)  //L'eccezion non è dovuta a una chiusura del server ma del client
                    {
                        GetUsersLock();
                        users.Remove(user);
                        activeThreads.Remove(Thread.CurrentThread);
                        chatDialog.Invoke(showMessage, Environment.NewLine + "--SERVER INFORMATION: User '" + user + "' disconnected!--" + Environment.NewLine);
                        SendMsgToAll(Environment.NewLine + "--SERVER INFORMATION: User '" + user + "' disconnected!--" + Environment.NewLine);
                        DecreaseClientNum(); //svegliare il thread che accetta connessioni nel caso in cui il numero di connessi sia passato da 4 a 3
                        ReleaseUsersLock();
                    }
                    if (locked)
                        ReleaseUsersLock(); //se ce l'avevo già prima, lo rilascio
                    c.Close();
                    videoClient.Close();
                    clipClient.Close();
                }
            }
            else
            {
                GetUsersLock();
                activeThreads.Remove(Thread.CurrentThread);
                ReleaseUsersLock();
            }
        }

        void receiveClipboard(object arg)
        {
            SetClipboardCallback setClipCallback = new SetClipboardCallback(chatDialog.setClipboard);
            ClientClass myClient = (ClientClass)arg;
            TcpClient clipClient = myClient.ClipClient;
            BinaryReader bReader = new BinaryReader(clipClient.GetStream());
            String type;
            try
            {
                while (true)
                {
                    //DataObject cliFile = new DataObject();
                    type = bReader.ReadString();
                    int dim = bReader.ReadInt32();
                    byte[] buffer = new byte[dim];
                    buffer = bReader.ReadBytes(dim);
                    if ((!type.Equals(DataFormats.Text)) && (!type.Equals(DataFormats.Bitmap)))
                    {
                        using (FileStream fs = new FileStream(type, FileMode.OpenOrCreate, FileAccess.Write))
                        {
                            fs.Write(buffer, 0, buffer.Length);
                            fs.Close();
                        }
                        
                        String percorso = Path.GetFullPath(type);
                        DataObject data = new DataObject();
                        data.SetData(DataFormats.FileDrop, true, new String[]{percorso});
                        chatDialog.Invoke(setClipCallback, data);
                        //chatDialog.setPath(percorso);
                    }
                    else
                    {
                        using (MemoryStream ms = new MemoryStream())
                        {
                            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter binForm = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                            ms.Write(buffer, 0, buffer.Length);
                            ms.Seek(0, SeekOrigin.Begin);
                            DataObject data = new DataObject();
                            data.SetData(type, binForm.Deserialize(ms));
                            chatDialog.Invoke(setClipCallback, data);
                        }
                    }
                    chatDialog.Invoke(showMessage, Environment.NewLine + "--SERVER INFORMATION: New clipboard content available: " + type + "--" + Environment.NewLine);
                    SendClipToAll(clipClient, type, buffer);
                }
            }
            catch (SocketException)
            {
            }
            catch (IOException)
            {
            }
            finally
            {
                myClient.CloseAll();
            }
        }

        

        private void DecreaseClientNum()
        {
            GetUsersLock();
            clientNum--;
            if (clientNum == MAX_CLIENTS - 1)
                clientEvent.Set();
            else if (clientNum == 0)
                transmit = false;
            ReleaseUsersLock();
        }

        /// <summary>
        /// ottiene il lock sulla regione critica degli utenti connessi
        /// </summary>
        public void GetUsersLock()
        {
            Monitor.Enter(usersMonitor);
        }

        /// <summary>
        /// Invia a tutti i client connessi un messaggio
        /// </summary>
        /// <param name="msg">il messaggio da inviare</param>
        public void SendMsgToAll(string msg)
        {
            GetUsersLock();
            ICollection clients = users.Values;
            if (clients.Count > 0)
            {
                IEnumerator enumerator = clients.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    try{
                        TcpClient c = ((ClientClass)enumerator.Current).ChatClient;
                        BinaryWriter bWriter = new BinaryWriter(c.GetStream());
                        StreamWriter writer = new StreamWriter(c.GetStream());
                        writer.WriteLine(msg);
                        writer.Flush();
                    }
                    catch
                    {
                    }
                }
            }
            ReleaseUsersLock();
        }
        /// <summary>
        /// Invia la clipboard a tutti i client connessi
        /// </summary>
        public void SendClipToAll(TcpClient notHim, string type, byte[] buffer) 
        {
            GetUsersLock();
            ICollection clients = users.Values;
            if (clients.Count > 0)
            {
                IEnumerator enumerator = clients.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    try
                    {
                        TcpClient c = ((ClientClass)enumerator.Current).ClipClient;
                        if (c != notHim)
                        {
                            BinaryWriter bWriter = new BinaryWriter(c.GetStream());
                            bWriter.Write(type);
                            bWriter.Write(buffer.Length);
                            bWriter.Write(buffer);
                            bWriter.Flush();
                        }
                    }
                    catch
                    {
                    }
                }
                chatDialog.Invoke(showMessage, Environment.NewLine + "--SERVER INFORMATION: Clipboard content " + type + " sent--" + Environment.NewLine);
            }
            else
                chatDialog.Invoke(showMessage, Environment.NewLine + "--SERVER INFORMATION: Clipboard wasn't sent, no clients connected--" + Environment.NewLine);
            ReleaseUsersLock();
        }

        public void SendFirstFrame(Bitmap frame)
        {
            GetUsersLock();
            ICollection clients = users.Values;
            if (clients.Count > 0)
            {
                IEnumerator enumerator = clients.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    try
                    {
                        if (transmit)
                        {
                            TcpClient c = ((ClientClass)enumerator.Current).VideoClient;
                            BinaryWriter writer = new BinaryWriter(c.GetStream());
                            writer.Write(FIRST_FRAME);
                            //writer.Write(x);
                            //writer.Write(y);
                            using (MemoryStream ms = new MemoryStream())
                            {
                                frame.Save(ms, ImageFormat.Jpeg);//Png);
                                byte[] bytes = ms.ToArray();
                                writer.Write(bytes.Length);
                                writer.Write(bytes);
                            }
                            writer.Flush();
                        }
                    }
                    catch
                    {
                    }

                }
            }
            ReleaseUsersLock();
        }

        public void SendModifyFrame(Bitmap frame, int x, int y)
        {
            GetUsersLock(); //FORSE MEGLIO UN ALTRO MONITOR
            ICollection clients = users.Values;
            if (clients.Count > 0)
            {
                IEnumerator enumerator = clients.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    try
                    {
                        if (transmit)
                        {
                            TcpClient c = ((ClientClass)enumerator.Current).VideoClient;
                            BinaryWriter writer = new BinaryWriter(c.GetStream());
                            writer.Write(FRAME_MODIFY);
                            writer.Write(x);
                            writer.Write(y);
                            using (MemoryStream ms = new MemoryStream())
                            {
                                frame.Save(ms, ImageFormat.Jpeg);//Png);
                                byte[] bytes = ms.ToArray();
                                writer.Write(bytes.Length);
                                writer.Write(bytes);
                            }
                            writer.Flush();
                        }
                    }
                    catch
                    {
                    }

                }
            }
            ReleaseUsersLock();
        }


        /// <summary>
        /// rilascia il lock sulla regione critica degli utenti connessi
        /// </summary>
        public void ReleaseUsersLock()
        {
            Monitor.Exit(usersMonitor);
        }

        // Gestione immagini
        private void HandleFrame(object obj)
        {
            Object[] myParams = (Object[])obj;
            Rectangle rect = (Rectangle)myParams[0];
            AutoResetEvent completed = (AutoResetEvent)myParams[1];
            Int32[] lastPixels = (Int32[])myParams[2];
            Int32[] currentPixels = (Int32[])myParams[3];

            int bmpHeight = (int)myParams[4];
            int bmpWidth = (int)myParams[5];

            Rectangle ris = CaptureClass.ComparePixels(lastPixels, currentPixels, rect, bmpHeight, bmpWidth);
            if (ris.Width > 0 && ris.Height > 0)
            {
                Bitmap bmp = new Bitmap(ris.Width, ris.Height);
                using (Graphics gfx = Graphics.FromImage(bmp))
                {
                    lock(tmpCurrent)
                        gfx.DrawImage(tmpCurrent, 0, 0, ris, GraphicsUnit.Pixel);
                }

                SendModifyFrame(bmp, ris.X, ris.Y);
                bmp.Dispose();
            }
            completed.Set();
        }

        private void imageMain()
        {
            WaitHandle[] nextFrameOrQuit = new WaitHandle[2];
            nextFrameOrQuit[0] = stopWaitingAndExit;
            nextFrameOrQuit[1] = nextFrameEvent;

            WaitHandle[] notPausedOrQuit = new WaitHandle[2];
            notPausedOrQuit[0] = stopWaitingAndExit;
            notPausedOrQuit[1] = notPausedEvent;
            while (keepWorking)
            {
                bool minimized = false;
                int which = WaitHandle.WaitAny(notPausedOrQuit);
                if (which == 0)
                    break;
                Size currentSize = new Size();
                if (startCompare == false)
                {
                    Monitor.Enter(bitmapMonitor);
                    if (windowCapture)
                    {
                        currentBitmap = CaptureClass.CaptureWindow(capturedWindow, out minimized);
                        if (currentBitmap == null && !minimized)
                        {
                            Monitor.Exit(bitmapMonitor);
                            requestClose();
                            break;
                        }
                    }
                    else
                        currentBitmap = CaptureClass.CaptureScreen(screenRectangle);
                    if (currentBitmap != null)
                    {
                        currentSize = currentBitmap.Size;
                        tmpCurrent = new Bitmap(currentBitmap); //mi creo una copia per permettere ai singoli thread di inviare il contenuto dei rettangoli modificati, altrimenti non riuscirebbero perchè la bitmp risulta lockata
                    }
                    Monitor.Exit(bitmapMonitor);
                    if (!minimized)
                    {
                        videoPaused = false;
                        if (!userPaused)
                        {
                            startCompare = true;
                            GetUsersLock();
                            if (users.Count > 0)
                                transmit = true;
                            ReleaseUsersLock();
                            mainForm.Invoke(setBitmapcallback, lastBitmap);
                            SendFirstFrame(currentBitmap);
                        }
                    }
                    else
                    {
                        if(!videoPaused)
                            pauseVideoTransmission(false);
                    }

                }
                else
                {
                    which = WaitHandle.WaitAny(nextFrameOrQuit);
                    if (which == 0)
                        break;
                    if (waitPictureThreads != null)
                    {
                        WaitHandle.WaitAll(waitPictureThreads);

                        lastBitmap.UnlockBits(lastBmpData);
                        currentBitmap.UnlockBits(currentBmpData);
                    }
                    Monitor.Enter(bitmapMonitor);
                    if (lastBitmap != null) //lastBitmap può essere null se la finestra caturata era inizialmente minimizzata
                        lastBitmap.Dispose();
                    if(tmpCurrent != null)
                        tmpCurrent.Dispose();
                    lastBitmap = currentBitmap;
                    if (keepWorking)
                    {
                        if (windowCapture)
                        {
                            currentBitmap = CaptureClass.CaptureWindow(capturedWindow, out minimized);
                            if (currentBitmap == null && !minimized)
                            {
                                Monitor.Exit(bitmapMonitor);
                                requestClose();
                                break;
                            }
                        }
                        else
                            currentBitmap = CaptureClass.CaptureScreen(screenRectangle);

                        if (currentBitmap != null)
                        {
                            tmpCurrent = new Bitmap(currentBitmap); //mi creo una copia per permettere ai singoli thread di inviare il contenuto dei rettangoli modificati, altrimenti non riuscirebbero perchè la bitmp risulta lockata
                            currentSize = currentBitmap.Size;
                            Bitmap tmp = new Bitmap(currentBitmap);
                            Monitor.Exit(bitmapMonitor);
                            try
                            {
                                if (!userPaused)
                                    mainForm.Invoke(setBitmapcallback, tmp);
                            }
                            catch
                            {
                            }
                            finally
                            {
                                tmp.Dispose();
                            }
                        }
                        else
                            Monitor.Exit(bitmapMonitor);
                    }
                    else
                        Monitor.Exit(bitmapMonitor);
                    if(minimized)
                        pauseVideoTransmission(false);

                }

                if (keepWorking/* && transmit*/)
                {
                    if (waitPictureThreads != null)
                    {
                        for (int i = 0; i < waitPictureThreads.Length; i++)
                            waitPictureThreads[i].Dispose();
                        waitPictureThreads = null;
                    }
                    if (transmit)
                    {
                        if ((lastBitmap == null) || (!lastBitmap.Size.Equals(currentBitmap.Size))) //lastBitmap può essere null se la finestra caturata era inizialmente minimizzata
                        {
                            if(currentBitmap != null) //Anche currentBitmp può essere null se la finestra era inizialmente minimizzata. In questo caso, il first frame viene direttamente inviato alla connssione del client
                                SendFirstFrame(currentBitmap);
                        }
                        else
                        {
                            lastBmpData = lastBitmap.LockBits(new Rectangle(0, 0, lastBitmap.Width, lastBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
                            currentBmpData = currentBitmap.LockBits(new Rectangle(0, 0, currentBitmap.Width, currentBitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

                            int numPixels = lastBmpData.Width * lastBmpData.Height;

                            Int32[] lastPixels = new Int32[numPixels];
                            Int32[] currentPixels = new Int32[numPixels];

                            Marshal.Copy(lastBmpData.Scan0, lastPixels, 0, numPixels);
                            Marshal.Copy(currentBmpData.Scan0, currentPixels, 0, numPixels);

                            int numRows = currentSize.Height / MaxThreadHeight;
                            if ((currentSize.Height % MaxThreadHeight) > (MaxThreadHeight / 2) || numRows == 0)
                                numRows++;

                            int numCols = currentSize.Width / MaxThreadWidth;
                            if ((currentSize.Width % MaxThreadWidth) > (MaxThreadWidth / 2) || numCols == 0)
                                numCols++;
                            int count = 0;
                            int numThreads = numRows * numCols;

                            if (waitPictureThreads == null)
                                waitPictureThreads = new WaitHandle[numThreads];

                            int singleThreadWidth = currentSize.Width / numCols;
                            int singleThreadHeight = currentSize.Height / numRows;

                            for (int i = 0; i < numRows; i++)
                            {
                                int threadY1 = i * singleThreadHeight;
                                int threadY2;
                                //int threadHeight;
                                if (i == numRows - 1)
                                    threadY2 = currentSize.Height - 1;
                                else
                                    threadY2 = singleThreadHeight + threadY1 - 1;

                                for (int j = 0; j < numCols; j++)
                                {
                                    int threadX1 = j * singleThreadWidth;
                                    int threadX2;
                                    //int threadWidth;
                                    if (j == numCols - 1)
                                        threadX2 = currentSize.Width - 1;
                                    else
                                        threadX2 = singleThreadWidth + threadX1 - 1;

                                    AutoResetEvent completedEvent = new AutoResetEvent(false);
                                    Rectangle threadRectangle = new Rectangle(threadX1, threadY1, threadX2 - threadX1 + 1, threadY2 - threadY1 + 1);
                                    waitPictureThreads[count++] = completedEvent;
                                    Object[] threadParams = new Object[6];
                                    threadParams[0] = threadRectangle;
                                    threadParams[1] = completedEvent;
                                    threadParams[2] = lastPixels;
                                    threadParams[3] = currentPixels;
                                    threadParams[4] = currentSize.Height;
                                    threadParams[5] = currentSize.Width;
                                    if (transmit)
                                        ThreadPool.QueueUserWorkItem(new WaitCallback(HandleFrame), threadParams);
                                }
                            }
                        }
                    }
                }
            }
        }

        public void requestClose()
        {
            keepWorking = false;
            stopWaitingAndExit.Set();  //Se ci sono 4 client connessi, il listener è in attesa che uno si disconnetta. con questo evento smette di aspettare e esce
            listener.Stop();
            GetUsersLock();
            ICollection clients = users.Values;
            if (clients.Count > 0)
            {
                IEnumerator enumerator = clients.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    try
                    {
                        ClientClass client = (ClientClass)enumerator.Current;
                        client.CloseAll();
                    }
                    catch
                    {
                    }
                }
            }
            ReleaseUsersLock();
        }

        public void pauseVideoTransmission(bool user)
        {
            //if (!userPaused)
            //{
            GetUsersLock();
            if (!userPaused && !videoPaused)
            {
                if (user)
                    userPaused = user;
                else
                    videoPaused = !user;
                transmit = false;
                ICollection clients = users.Values;
                if (clients.Count > 0)
                {
                    IEnumerator enumerator = clients.GetEnumerator();
                    while (enumerator.MoveNext())
                    {
                        try
                        {
                            TcpClient c = ((ClientClass)enumerator.Current).VideoClient;
                            BinaryWriter writer = new BinaryWriter(c.GetStream());
                            writer.Write(TRANSMISSION_PAUSED);
                        }
                        catch
                        {
                        }
                    }
                }
                ReleaseUsersLock();

                //Setta l'immagine "Trasmissione sospesa"
                Monitor.Enter(bitmapMonitor);
                transmit = false;
                if (userPaused)
                    notPausedEvent.Reset();
                Bitmap bmp = Properties.Resources.sospesa;
                if (tmpCurrent != null)
                    tmpCurrent.Dispose();
                startCompare = false;
                tmpCurrent = new Bitmap(bmp);
                mainForm.Invoke(setBitmapcallback, bmp);
                Monitor.Exit(bitmapMonitor);
                //this.in
            }
            else
            {
                //if (!userPaused)
                //    userPaused = user;
                if (user)
                    userPaused = user;
                else
                    videoPaused = !user;
                ReleaseUsersLock();
            }
        }

        public void resumeVideoTransmission()
        {
            if (userPaused)
            {
                Monitor.Enter(bitmapMonitor);
                if (windowCapture)
                {
                    bool minimized;
                    lastBitmap = CaptureClass.CaptureWindow(capturedWindow, out minimized);
                    if (lastBitmap == null && !minimized)
                    {
                        Monitor.Exit(bitmapMonitor);
                        requestClose();
                        return;
                    }
                }
                else
                    lastBitmap = CaptureClass.CaptureScreen(screenRectangle);
                if (tmpCurrent != null)
                    tmpCurrent.Dispose();
                if (lastBitmap != null)
                    tmpCurrent = new Bitmap(lastBitmap);
                else
                    tmpCurrent = null;
                Monitor.Exit(bitmapMonitor);

                //GetUsersLock();
                //if(users.Count > 0)
                //    transmit = true;
                //ReleaseUsersLock();
                notPausedEvent.Set();
                userPaused = false;
            }
        }
    }
}
